package spw.main;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.w3c.dom.Node;

import spw.converter.SPWConverter;
import spw.enums.PropertiesFileType;
import spw.enums.SPWrequestStatus;
import spw.log4j.Log4jLogger;
import spw.model.SnowPowInfo;

public class SPWSender {
	static Logger log = Logger.getLogger(Log4jLogger.class.getName());
	static final long ONE_MINUTE_IN_MILLIS=60000;
	static int countSpwSender = 0;
	static boolean isDataSendOK=false;
	//TODO change boolean to false in PROD
	static boolean isTest=false;

	public static void executeSendDataToSPW(){
		Properties props = new Properties();
		FileInputStream in;
		try {
			in = new FileInputStream("info.properties");
			props.load(in);
			in.close();
		} catch (FileNotFoundException e1) {
			log.error(e1.getMessage());
		} catch (IOException e) {
			log.error(e.getMessage());
		}

		String driver = props.getProperty("jdbc.driver");
		String dbUrl = props.getProperty("jdbc.url");
		String username = props.getProperty("jdbc.username");
		String password = props.getProperty("jdbc.password");
		String propertyFileType = props.getProperty("filetype");
		String securysatJdbcAdditionalParam = null;
		//If it's securysat file we need to include additionnal parameter in the jdbc url
		if(PropertiesFileType.SECURYSAT.getValue().equals(propertyFileType)){
			securysatJdbcAdditionalParam = props.getProperty("jdbc.additionnalparam");
			dbUrl=dbUrl+securysatJdbcAdditionalParam;
		}

		String queryNewData = props.getProperty("querynew");//new data
		String queryOldData = props.getProperty("queryold");//old data NOT_SEND or SEND_NOK
		String queryCheckPosition = props.getProperty("querycheckposition");//get all position for a client in the spwvehiclemap table to compare it with new data and detect similar position
		String spwUrl=props.getProperty("spwurl");
		String spwServerUri=props.getProperty("spwserveruri");
		String spwLogin=props.getProperty("spwlogin");
		String spwPassword=props.getProperty("spwpassword");

		String hashedPassword = getM5Password(spwPassword);

		//Load Mysql jdbc driver
		try {
			Class.forName(driver);
		} catch ( ClassNotFoundException e ) {
			log.error("Error while loading, driver not found in the classpath! " + e.getMessage());
		}

		Connection connection = null;
		PreparedStatement pstm = null;

		ResultSet res = null;
		try {
			connection = DriverManager.getConnection(dbUrl,username,password);//getConnection( dbUrl, username, password );
			List<SnowPowInfo> snowPowInfos = new ArrayList<SnowPowInfo>();

			//Get new data to send to SPW.
			pstm = connection.prepareStatement(queryNewData);

			Date stop = new Date();
			long t=stop.getTime();
			Date start=new Date(t - (2 * ONE_MINUTE_IN_MILLIS));
			Timestamp tsstart =new Timestamp(start.getTime());
			Timestamp tsstop =new Timestamp(stop.getTime());

			//TODO delete when test will be finished
		
//				Timestamp tsstart =new Timestamp(1412148480000l);//aidoo test : '2014-10-01 09:28:00'
//				Timestamp tsstop =new Timestamp(1412148490000l);//aidoo test : '2014-10-01 09:29:00'
//				Timestamp tsstart =new Timestamp(1418656320000l);//securysat test : '12/15/2014 16:12:00'
//				Timestamp tsstop =new Timestamp(1418656335000l);//securysat test : '12/15/2014 16:12:15'


			tsstart.setNanos(0);
			tsstop.setNanos(0);
			int ix=1;
			pstm.setTimestamp(ix++,tsstart);
			pstm.setTimestamp(ix++,tsstop);
			res = pstm.executeQuery();
			Integer sequence = 1;
			while (res.next()) {
				SnowPowInfo snowPowInfo = null;
				try {
					snowPowInfo = new SnowPowInfo(res);
				} catch (Exception e) {
					log.error(e.getMessage());
				}
				if(snowPowInfo!=null){
					snowPowInfo.setDataToResend(false);//set to false if it's new data.
					snowPowInfos.add(snowPowInfo);
				}
			}

			//old data not sent yet (NOT_SEND) or sent with status SEND_NOK(add it at the end of the "snowPowInfos" list because, we have to send new data before old).
			pstm = connection.prepareStatement(queryOldData);
			res = pstm.executeQuery();

			while (res.next()){
				SnowPowInfo snowPowInfo = null;
				try {
					snowPowInfo = new SnowPowInfo(res);
				} catch (Exception e) {
					log.error(e.getMessage());
				}
				if(snowPowInfo!=null){
					snowPowInfo.setDataToResend(true);
					snowPowInfos.add(snowPowInfo);
				}
			}

			Set<Integer> checkGpsPositionIdList = new HashSet<Integer>();

			//put in a HashSet, gpsPositionId from spwvehiclemap table to check if a new position is not already registered in it.
			pstm = connection.prepareStatement(queryCheckPosition);
			res = pstm.executeQuery();
			while (res.next()) {
				Integer currentPositionId = res.getInt("GpspositionId");
				if(currentPositionId != null)checkGpsPositionIdList.add(currentPositionId);
			}

			for (SnowPowInfo spw : snowPowInfos) {
				countSpwSender = 0;
				isDataSendOK=false;
				Integer responseStatus;//SEND_OK(1), SEND_NOK(2), NOT_SEND(3);
				SOAPConnection soapConnection = null;
				SOAPMessage soapResponse=null;
				//if new data
				if(!spw.isDataToResend()){
					if(checkGpsPositionIdList.contains(spw.getGpsPositionId())){//if a new position is the spwvehiclemap table, we can't send it like a new position.
						continue;
					}
					//get max sequence id for a vehicle to set "sequence" variable or set it to 1 if it's registered for the first time or if the max sequence id registered is greater or equal than 99999
					pstm = connection.prepareStatement("select max(SequenceId) as 'maxSequId' from spwvehiclemap where vehicleId = ? and SpwId = ?");	
					int ixx=1;
					pstm.setInt(ixx++,spw.getVehicleId());
					pstm.setString(ixx++, spw.getSPWId());
					res = pstm.executeQuery();
					while (res.next()) {
						sequence = res.getInt("maxSequId");
					}
					if(sequence <= 0 || sequence >= 99999)sequence=1;
					else sequence=sequence + 1;

					spw.setSequenceId(sequence);
					String currentFrame = SPWConverter.convertToSPW(spw, propertyFileType);//vehicle information convert to SPW frame.
					spw.setFrame(currentFrame);
					if(!isTest){
						while(countSpwSender <3 && !isDataSendOK){
							soapResponse = sendDataToSPW(currentFrame, spwLogin, hashedPassword, spwServerUri, spwUrl, spw, soapConnection);
						}

						//TODO delete when test are over
						try {
							printSOAPResponse(soapResponse);
							//soapConnection.close();
						} catch (Exception e) {
							e.printStackTrace();
						}

						//if we have connection problems or other, status is set to "NOT_SEND", data save in spwvehiclemap with "NOT_SEND" status and will be send later.
						if(countSpwSender==3){
							saveData(pstm, spw, connection, SPWrequestStatus.NOT_SEND);
							countSpwSender = 0;
							isDataSendOK=false;
							continue;
						}

						//even if it's ok we reset variable for next treatment
						countSpwSender = 0;
						isDataSendOK=false;

						responseStatus = processReturnMesSPW(soapResponse);//2 possible status : SEND_OK(no problems) or SEND_NOK()
						//if status is SEND_NOK, we try to resend data 3 times with interval of 10 sec.
						if(SPWrequestStatus.SEND_NOK.getValue() == responseStatus){
							while(countSpwSender < 3 && !isDataSendOK){
								soapResponse = sendDataToSPW(currentFrame, spwLogin, hashedPassword, spwServerUri, spwUrl, spw, soapConnection);
								responseStatus = processReturnMesSPW(soapResponse);
								if(SPWrequestStatus.SEND_NOK.getValue() == responseStatus){
									countSpwSender++;
									isDataSendOK=false;
									try {
										//SPW specification (wait 10 seconds between 2 data sending)
										Thread.sleep(10000);
									} catch (InterruptedException e1) {
										log.error(e1.getMessage());
									}
								}
								else{
									isDataSendOK=true;
								}
							}
						}

						//if status is "SEND_NOK", we save data in spwvehicletable with "SEND_NOK" status and we try to resend it later.
						if(countSpwSender==3){
							saveData(pstm, spw, connection, SPWrequestStatus.SEND_NOK);
							countSpwSender = 0;
							isDataSendOK=false;
							continue;
						}

						//reset variable for next step in the loop
						countSpwSender = 0;
						isDataSendOK=false;
					}

					//if status is "SEND_OK" we save data in spwvehicletable with "SEND_OK" status.
					saveData(pstm, spw, connection, SPWrequestStatus.SEND_OK);
				}
				else{
					if(!isTest){
						//else, we have already saved the record in the spwvehiclemap (with a status NOT_SEND or STATUS_NOK) table previously because old sending fail,
						//so we have just to resend data and if it's ok, change status in the table to 'SEND_OK'
						soapResponse = sendDataToSPW(spw.getFrame(), spwLogin, hashedPassword, spwServerUri, spwUrl, spw, soapConnection);
						responseStatus = processReturnMesSPW(soapResponse);
						if(responseStatus==1){
							try{
								pstm = connection.prepareStatement("UPDATE spwvehiclemap set status = ?, Modifie_Le = ?, "
										+"Modifie_Auteur = ? where serverrecordstatus != 3 and accountid = ? and GpspositionId = ? ",java.sql.PreparedStatement.RETURN_GENERATED_KEYS);
								int ixx=1;
								pstm.setInt(ixx++, SPWrequestStatus.SEND_OK.getValue());
								pstm.setTimestamp(ixx++,new java.sql.Timestamp(System.currentTimeMillis()));
								pstm.setInt(ixx++,-1);
								pstm.setInt(ixx++,spw.getAccountId());
								pstm.setInt(ixx++,spw.getGpsPositionId());
								pstm.executeUpdate();
							}
							catch(Exception e){
								log.error(e.getMessage());
							}
						}
					}
				}
			}
		} catch ( SQLException e ) {
			log.error("test "+e.getMessage());
		}  finally {
			if ( res != null ) {
				try {
					res.close();
				} catch ( SQLException ignore ) {
					log.error(ignore.getMessage());
				}
			}
			if ( pstm != null ) {
				try {
					pstm.close();
				} catch ( SQLException ignore ) {
					log.error(ignore.getMessage());
				}
			}
			if ( connection != null ) {
				try {
					connection.close();
				} catch ( SQLException ignore ) {
					log.error(ignore.getMessage());
				}
			}
		}
	}

	/*******************************************************************************************/


	private static SOAPMessage createSOAPRequest(String frame, String login, String password, String serverURI) throws Exception {
		MessageFactory messageFactory = MessageFactory.newInstance();
		SOAPMessage soapMessage = messageFactory.createMessage();
		SOAPPart soapPart = soapMessage.getSOAPPart();

		// SOAP Envelope
		SOAPEnvelope envelope = soapPart.getEnvelope();
		envelope.addNamespaceDeclaration("test", serverURI);

		// SOAP Body
		SOAPBody soapBody = envelope.getBody();
		SOAPElement soapBodyElem = soapBody.addChildElement("sendTrame", "test");
		SOAPElement soapBodyElem1 = soapBodyElem.addChildElement("trame");
		soapBodyElem1.addTextNode(frame);
		SOAPElement soapBodyElem2 = soapBodyElem.addChildElement("login");
		soapBodyElem2.addTextNode(login);
		SOAPElement soapBodyElem3 = soapBodyElem.addChildElement("password");
		soapBodyElem3.addTextNode(password);


		MimeHeaders headers = soapMessage.getMimeHeaders();
		headers.addHeader("SOAPAction", serverURI  + "sendTrame");

		soapMessage.saveChanges();

		//Print the request message 
		System.out.print("Request SOAP Message = ");
		soapMessage.writeTo(System.out);
		System.out.println();

		return soapMessage;
	}

	private static void printSOAPResponse(SOAPMessage soapResponse) throws Exception {
		TransformerFactory transformerFactory = TransformerFactory.newInstance();
		Transformer transformer = transformerFactory.newTransformer();
		Source sourceContent = soapResponse.getSOAPPart().getContent();
		System.out.print("\nResponse SOAP Message = ");
		StreamResult result = new StreamResult(System.out);
		transformer.transform(sourceContent, result);
	}

	private static String getM5Password(String passwordToHash){
		String generatedPassword = null;
		try {
			// Create MessageDigest instance for MD5
			MessageDigest md = MessageDigest.getInstance("MD5");
			//Add password bytes to digest
			md.update(passwordToHash.getBytes());
			//Get the hash's bytes
			byte[] bytes = md.digest();
			//This bytes[] has bytes in decimal format;
			//Convert it to hexadecimal format
			StringBuilder sb = new StringBuilder();
			for(int i=0; i< bytes.length ;i++)
			{
				sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
			}
			//Get complete hashed password in hex format
			generatedPassword = sb.toString();
		}
		catch (NoSuchAlgorithmException e)
		{
			e.printStackTrace();
		}
		return generatedPassword;
	}


	private static SOAPMessage sendDataToSPW(String currentFrame, String spwLogin, String hashedPassword, String spwServerUri, String spwUrl, SnowPowInfo spw, SOAPConnection soapConnection){
		SOAPMessage soapResponse = null;
		//send data to SPW
		try {
			// Create SOAP Connection
			SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
			soapConnection = soapConnectionFactory.createConnection();
			// Send SOAP Message to SOAP Server
			String url = spwUrl;
			soapResponse = soapConnection.call(createSOAPRequest(currentFrame, spwLogin, hashedPassword, spwServerUri), url);


		} catch (Exception e) {
			log.error(e.getMessage()+"vehicleId : "+spw.getVehicleId()+" - gpspositionID : "+spw.getGpsPositionId()+" - sequenceId"+spw.getSequenceId());
			countSpwSender++;
			isDataSendOK=false;
			return null;
		}
		isDataSendOK=true;
		return soapResponse;
	}

	private static Integer processReturnMesSPW(SOAPMessage soapResponse){
		// Process the SOAP Response
		String SPWReturnValue=null;
		Integer requestStatus=null;
		try {
			SOAPBody soapBody=soapResponse.getSOAPBody();
			Node firstElement=soapBody.getFirstChild();
			if (firstElement != null) {
				SPWReturnValue=firstElement.getTextContent();
			}
		}
		catch (SOAPException e) {
			log.debug("Unabled to get the first body element from the outbound dispatch message",e);
		}
		String CCVreturned=null;
		String returnValueToCheck=null;
		if(SPWReturnValue!=null && !"".equals(SPWReturnValue)){
			String[] parts = SPWReturnValue.split("#");
			returnValueToCheck = parts[0].trim();
			CCVreturned = parts[1].trim(); // STRING_VALUES
			CCVreturned = CCVreturned.substring(0, CCVreturned.length()-1);
		}

		if(CCVreturned != null && returnValueToCheck != null){
			if(CCVreturned.equals(SPWConverter.checkContent(returnValueToCheck))){
				requestStatus = SPWrequestStatus.SEND_OK.getValue();
			}
			else{
				requestStatus = SPWrequestStatus.SEND_NOK.getValue();
			}
		}
		else{
			requestStatus = SPWrequestStatus.SEND_NOK.getValue();
		}
		return requestStatus;
	}

	private static void saveData(PreparedStatement pstm, SnowPowInfo spw, Connection connection,SPWrequestStatus status) throws SQLException{
		try{	
			pstm = connection.prepareStatement("INSERT INTO spwvehiclemap (VehicleId,SpwId,status,gpspositionId,Frame, sequenceId,Cree_Le,Modifie_Le,Cree_Auteur, "
					+"Modifie_Auteur,ServerRecordStatus,AccountId) VALUES (?,?,?,?,?,?,?,?,?,?,?,?) ",java.sql.PreparedStatement.RETURN_GENERATED_KEYS);
			int ix=1;
			pstm.setInt(ix++, spw.getVehicleId());
			pstm.setString(ix++, spw.getSPWId());
			pstm.setInt(ix++, status.getValue());
			pstm.setInt(ix++, spw.getGpsPositionId());
			pstm.setString(ix++, spw.getFrame());
			pstm.setInt(ix++, spw.getSequenceId());
			pstm.setTimestamp(ix++,new java.sql.Timestamp(System.currentTimeMillis()));
			pstm.setTimestamp(ix++,new java.sql.Timestamp(System.currentTimeMillis()));
			pstm.setInt(ix++,-1);
			pstm.setInt(ix++,-1);
			pstm.setInt(ix++,1);
			pstm.setInt(ix++,spw.getAccountId());
			pstm.executeUpdate();
		}
		catch(Exception e){
			log.error(e.getMessage());
		}
	}
}
